#!/usr/bin/python3

import socket
import codecs

## define Request and Responce classes to simplify the actual code

class Request:
    def __init__(self, request_data):
        try:
            decoded_request = request_data.decode("UTF-8")
            self.successful = True

        except Exception as e:
            print("Failed to decode request ({})".format(e))
            self.successful = False

        if self.successful:
            self.method = decoded_request.split(" ")[0]
            self.path = decoded_request.split(" ")[1]

class Responce:
    def __init__(self):
        self.code = ""
        self.content_type = ""
        self.body = ""

    def generate(self):
        responce_data = "HTTP/1.1 {}\r\nContent-Type: {}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}".format(self.code, self.content_type, len(self.body.encode("UTF-8")), self.body)
        return responce_data.encode("UTF-8")


## defining intify() function to make code reading easier
## and to create a failsafe wrapper around the int() built-in function

def intify(x):
    try:
        return int(x)
    except Exception as e:
        print("Failed to parse integer from {} ({})".format(x, e))
        return False

## defining readfile() function to make code easier to read

def readfile(path):
    try:
        return codecs.open(path, "r", encoding = "UTF-8").read()

    except FileNotFoundError:
        print("No such file: {}".format(path))
        return False

    except Exception as e:
        print("Failed to read file {} ({})".format(path, e))
        return False


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind( ("", 8080) )
s.listen()

print("[INFO] Server started")

while True:
    try:
        conn, addr = s.accept()

        ## allowing very high time intervals as they
        ## are possible on slow networks during
        ## high loads (or during war)
        conn.settimeout(1800)
        
        connection_alive = True
        request_buffer = bytes()

        ## receiving request
        
        while True:
            new_data = conn.recv(1024)

            if len(new_data) == 0:
                ## connection closed, cleaning up
                conn.close()
                connection_alive = False
                break

            request_buffer += new_data

            if request_buffer[-4:] == "\r\n\r\n".encode("UTF-8"):
                ## request finished, stopping the loop
                break

        ## check if client is still there

        if not connection_alive:
            print("[LOG] Lost connection from {}".format(addr[0] + ":" + str(addr[1])))
            conn.close()
            continue

        ## processing the request

        req = Request(request_buffer)

        ## if it is not a proper request, ignore it
        if not req.successful:
            conn.close()
            continue

        ## logging requests to terminal

        print("[LOG] {} - {} {}".format( addr[0] + ":" + str(addr[1]), req.method, req.path ))

        if req.method == "GET":
            ## giving index page at /
            if req.path == "/":
                responce_body = readfile("www/index.html")

                if responce_body == False:
                    conn.close()
                    continue

                res = Responce()
                res.code = "200"
                res.content_type = "text/html"
                res.body = responce_body

                responce_data = res.generate()
                conn.send(responce_data)

                conn.close()

            ## giving requested amount of lines in the end of the file at /last/*
            elif req.path[:6] == "/last/":
                raw_request_amount = req.path.split("/")[2]
                x = intify(raw_request_amount)

                if x == False:
                    conn.close()
                    continue

                file_content = readfile("files/links.txt")

                if file_content == False:
                    conn.close()
                    continue

                file_lines = file_content.split("\n")

                res = Responce()
                res.code = "200"
                res.content_type = "text/plain"
                res.body = "\r\n".join(file_lines[-x-1:]) # selecting requested amount of lines

                responce_data = res.generate()
                conn.send(responce_data)

                conn.close()

            ## giving the whole file at /all
            elif req.path == "/all":
                responce_body = readfile("files/links.txt")

                if responce_body == False:
                    conn.close()
                    continue

                res = Responce()
                res.code = "200"
                res.content_type = "text/plain"
                res.body = responce_body

                responce_data = res.generate()
                conn.send(responce_data)

                conn.close()

            ## at everything else return 404 Not Found error
            else:
                res = Responce()
                res.code = "404"
                res.content_type = "text/plain"
                res.body = "{} is not a supported request path".format(req.path)

                responce_data = res.generate()
                conn.send(responce_data)

                conn.close()

        ## HEAD is not supported yet to reduce debug time and possible error surface
        elif req.method == "HEAD":
            res = Responce()
            res.code = "501"
            res.content_type = "text/plain"
            res.body = "{} is not a supported request method - yes, it IS intentional (and yes, it WILL be added in upcoming releases; it's not there yet to make a working release faster by reducing the amount of code to debug and polish)".format(req.method)

            responce_data = res.generate()
            conn.send(responce_data)

            conn.close()

        ## other methods are not supported yet
        else:
            res = Responce()
            res.code = "501"
            res.content_type = "text/plain"
            res.body = "{} is not a supported request method".format(req.method)

            responce_data = res.generate()
            conn.send(responce_data)

            conn.close()

    ## handling Ctrl+C signal
    except KeyboardInterrupt:
        print("\r[INFO] Exiting properly")
        break

    ## show thread exception as non-critical error
    except Exception as e:
        print("[ERROR] Got exception {}".format(e))
        conn.close()
        continue
